//#define _MY_DEBUG
#ifdef _MY_DEBUG
#pragma warning(disable:4996) 
#pragma warning(disable:4127) 
#include "../helpers/bitmap_image.hpp"
#define LOGT(arg) do { printf("%.2f ", m_game->match->timePassed); printf arg; } while (0)
#define LOG(arg) do { printf arg; } while (0)
#else
#define LOGT(arg) 
#define LOG(arg) 
#endif
#include <string>
#include <boost/none.hpp>
#include <boost/thread.hpp>

#include "api/GameInfo.h"
#include "api/Commands.h"
#include "api/Commander.h"
#include "api/CommanderFactory.h"

#define PI 3.141592f

#include "PathFinder.h"


using namespace std;


class MyCommander : public Commander
{
public:
    virtual string getName() const;
    virtual void initialize();
    virtual void tick();
    virtual void shutdown();


private:
    void ReassignBotRoles();

    uint GetOptimalNumDefenders();
    
    void UpdateFacts();
    void UpdateDefenders();
    void UpdateAssaults();
    void UpdateRunners();

    //
    bool HandleMatchEvents();
    void SetFlagPossessions();

    uint FindClosestBot(vector<BotInfo*> bots, const Vector2& pos);

    void SetDefendDirs();
    BotInfo* CheckEnemyGuardingFlag();

    // debug
    void DumpCreate();
    void DumpDestroy(string name);
    void DumpImage();
    void DumpPath(const Path& path, uchar r, uchar g, uchar b);

    bool SeeDanger(BotInfo* bot);


    bool m_MyPossession;
    bool m_EnemyPossession;
    bool m_SituationChanged;

    uint m_EnemyBotsAlive;

    // data used by defenders
    vector<Vector2> m_BaseDefenceDirs;
    bool m_DirsChanged;
    map<string, vector<Vector2> > m_BotDefenceDirs;
    float m_DefendersRatio;


    vector<BotInfo*> m_Defenders;
    vector<BotInfo*> m_Runners;
    vector<BotInfo*> m_Assaults;

    map<string, Vector2> m_BotDest;

    Path m_Bot1Path;

    PathFinder* m_PathFinder;


#ifdef _MY_DEBUG
    bitmap_image* m_DebugImage;
#endif // _MY_DEBUG
};

REGISTER_COMMANDER(MyCommander);


string MyCommander::getName() const
{
    return "JohnyBot v1"; 
}


void MyCommander::initialize()
{
    DumpCreate();
    DumpImage();

    m_EnemyPossession = false;
    m_MyPossession = false;
    m_SituationChanged = true;

    m_EnemyBotsAlive = m_game->enemyTeam->members.size();

    m_DefendersRatio = 0.5f;
    m_DirsChanged = true;

    SetDefendDirs();
    m_BotDefenceDirs.clear();

    m_PathFinder = new PathFinder(&*m_level);
    
    auto spawn_area = m_game->team->botSpawnArea;
    LOGT(("spawnarea:%f, %f, %f, %f\n", spawn_area.first.x, spawn_area.first.y, spawn_area.second.x, spawn_area.second.y));
    LOGT(("flag spawn location: %f, %f\n", m_game->team->flagSpawnLocation.x, m_game->team->flagSpawnLocation.y));

    /*
    m_PathFinder->FindPath(*m_Bot1->position, m_EnemyFlagPosition, m_Bot1Path, false);
    DumpPath(m_Bot1Path, 255, 255, 0);
    m_Bot1Path.clear();
    m_PathFinder->FindPath(*m_Bot1->position, m_EnemyFlagPosition, m_Bot1Path);
    DumpPath(m_Bot1Path, 255, 0, 0);

    LOG(("Path size:%d", m_Bot1Path.size()));
    for (uint i = 0; i < m_Bot1Path.size(); ++i)
    {
        LOG(("[%.1f, %.1f] ", m_Bot1Path[i].x, m_Bot1Path[i].y));
    }
    LOG(("\n"));
    */

    DumpDestroy("color_map_image.bmp");
}

void MyCommander::tick()
{
    UpdateFacts();
    ReassignBotRoles();
    //LOG(("Defenders: %d, Assault: %d, Runners: %d, EnemyBotsAlive: %d\n", m_Defenders.size(), m_Assaults.size(), m_Runners.size(), m_EnemyBotsAlive));

    UpdateDefenders();
    UpdateAssaults();
    UpdateRunners();
}

void MyCommander::shutdown()
{
    delete m_PathFinder;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::ReassignBotRoles()
{
    if (!m_SituationChanged)
        return;

    m_SituationChanged = false;

    m_Defenders.clear();
    m_Assaults.clear();
    m_Runners.clear();

    vector<BotInfo*> bots_available = m_game->bots_alive;
    if (bots_available.size() == 0)
        return;

    if (m_MyPossession) // i'm in possession of enemy flag
    {
        BotInfo* bot_with_flag = NULL;
        auto it = bots_available.begin();
        for (; it != bots_available.end(); ++it)
        {
            BotInfo* bot = *it;
            if (bot->health > 0.0f && bot->flag)
            {
                bot_with_flag = bot;
                break;
            }
        }

        m_Runners.push_back(bot_with_flag);
        bots_available.erase(it);
    }

    if (m_EnemyBotsAlive == 0 && bots_available.size() > 0)
    {
        Vector2 enemy_flag = m_game->enemyTeam->flag->position;
        uint index = FindClosestBot(bots_available, enemy_flag);

        m_Runners.push_back(bots_available[index]);
        auto it = bots_available.begin();
        advance(it, index);
        bots_available.erase(it);
    }

    if (m_EnemyPossession) // enemy in possession of my flag, all available bots become assaults 
    {
        for (uint i = 0; i < bots_available.size(); ++i)
            m_Assaults.push_back(bots_available[i]);

        return;
    }

    if (!m_EnemyPossession) // my flag in my possession
    {
        uint num_defenders = GetOptimalNumDefenders();

        if (num_defenders >= bots_available.size())
        {
            for (uint i = 0; i < bots_available.size(); ++i)
                m_Defenders.push_back(bots_available[i]);

            return;
        }

        // find num_defenders bots closest to the flag
        for (uint i = 0; i < num_defenders; ++i)
        {
            Vector2 my_flag_pos = m_game->team->flag->position;

            uint index = FindClosestBot(bots_available, my_flag_pos);

            m_Defenders.push_back(bots_available[index]);
            auto it = bots_available.begin();
            advance(it, index);
            bots_available.erase(it);
        }

        // set data used by defenders
        m_BotDefenceDirs.clear();
        uint num_dirs_per_defender = m_BaseDefenceDirs.size() / num_defenders;

        uint dir_index = 0;
        for (uint i = 0; i < num_defenders; ++i) // assume less defenders than directions
        {
            for (uint j = 0; j < num_dirs_per_defender; ++j)
            {
                m_BotDefenceDirs[m_Defenders[i]->name].push_back(m_BaseDefenceDirs[dir_index]);
                dir_index++;
                if (dir_index >= m_BaseDefenceDirs.size())
                    dir_index = 0;
            }

        }
        m_DirsChanged = true;

    }

    for (uint i = 0; i < bots_available.size(); ++i)
        m_Assaults.push_back(bots_available[i]);

    LOGT(("ReassignBotRoles defenders: %d, assaults: %d, runners: %d\n", m_Defenders.size(), m_Assaults.size(), m_Runners.size()));
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::UpdateFacts()
{
    bool res = HandleMatchEvents();
    if (res)
        LOGT(("my possession: %d, enemy possession: %d, enemies alive: %d (from events)\n", m_MyPossession, m_EnemyPossession, m_EnemyBotsAlive));

    m_SituationChanged = res;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::UpdateDefenders()
{
    Vector2 flag_position = m_game->team->flag->position;

    for (uint i = 0; i < m_Defenders.size(); ++i)
    {
        BotInfo* bot = m_Defenders[i];

        if (bot->state != BotInfo::STATE_IDLE && !m_DirsChanged)
            continue;

        if (bot->position->distance(flag_position) > 5.f)
        {
            issue(new AttackCommand(bot->name, flag_position, boost::none, "Defender"));
            m_BotDest[bot->name] = flag_position;
        }
        else
        {
            DefendCommand::FacingDirectionVector facing_vector;
            vector<Vector2> dirs = m_BotDefenceDirs[m_Defenders[i]->name];
            for (uint j = 0; j < dirs.size(); ++j)
            {
                pair<Vector2, float> p(pair<Vector2, float>(dirs[j], 0.01f));// TODO what's the minimal time?
                facing_vector.push_back(p);
            }

            issue(new DefendCommand(bot->name, facing_vector, "Defender"));
        }
    }
    m_DirsChanged = false;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::UpdateAssaults()
{
    for (uint i = 0; i < m_Assaults.size(); ++i)
    {
        BotInfo* bot = m_Assaults[i];
        Vector2 enemy_flag_pos = m_game->enemyTeam->flag->position;

        if (Utils::IsLOS(&*m_level, *bot->position, enemy_flag_pos)
            && m_BotDest[bot->name].distance(enemy_flag_pos) > 1.f
            && !SeeDanger(bot))
        {
            issue(new AttackCommand(bot->name, enemy_flag_pos, boost::none,"Assault"));
            m_BotDest[bot->name] = enemy_flag_pos;
        }
        else
        {
            if (bot->state != BotInfo::STATE_IDLE)
                continue;

            Vector2 res;
            m_level->findRandomFreePositionInBox(res, Vector2(0.f, 0.f), Vector2((float) m_level->width, (float) m_level->height));

            issue(new AttackCommand(bot->name, res, boost::none,"Assault"));
            m_BotDest[bot->name] = res;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::UpdateRunners()
{
    Vector2 enemy_flag_pos = m_game->enemyTeam->flag->position;
    Vector2 score_location = m_game->team->flagScoreLocation;

    for (uint i = 0; i < m_Runners.size(); ++i)
    {
        BotInfo* bot = m_Runners[i];
        Vector2 bot_dest = m_BotDest[bot->name];

        if (bot->state != BotInfo::STATE_IDLE
            && (bot_dest.distance(enemy_flag_pos) < 1.f || bot_dest.distance(score_location) < 1.f)) // bot is already running for the flag or is returning the flag
            continue;

        if (bot->flag)
        {
            issue(new MoveCommand(bot->name, score_location, "Runner"));
            m_BotDest[bot->name] = score_location;
        }
        else
        {
            issue(new MoveCommand(bot->name, enemy_flag_pos, "Runner"));
            m_BotDest[bot->name] = enemy_flag_pos;
        }
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
bool MyCommander::HandleMatchEvents()
{
    bool res = false;
    std::vector<MatchCombatEvent>& combat_events = m_game->match->combatEvents;

    for (auto it = combat_events.begin(); it != combat_events.end(); ++it)
    {
        res = true;

        switch (it->type)
        {
        case MatchCombatEvent::TYPE_FLAG_PICKEDUP:
            if(it->flagPickupEventData.subject == m_game->enemyTeam->flag)
            {
                LOGT(("enemy flag pickedup (%f)\n", it->time));
                m_MyPossession = true;
            }
            else if (it->flagPickupEventData.subject == m_game->team->flag)
            {
                LOGT(("my flag pickedup (%f)\n", it->time));
                m_EnemyPossession = true;
            }
            break;

        case MatchCombatEvent::TYPE_FLAG_DROPPED:
            if(it->flagDroppedEventData.subject == m_game->enemyTeam->flag)
            {
                LOGT(("enemy flag dropped (%f)\n", it->time));
                m_MyPossession = false;
            }
            else if (it->flagDroppedEventData.subject == m_game->team->flag)
            {
                LOGT(("my flag dropped (%f)\n", it->time));
                m_EnemyPossession = false;
            }
            break;

        case MatchCombatEvent::TYPE_FLAG_CAPTURED:
            if(it->flagCapturedEventData.subject == m_game->enemyTeam->flag)
            {
                LOGT(("enemy flag captured (%f)\n", it->time));
                m_MyPossession = false;
            }
            else if (it->flagCapturedEventData.subject == m_game->team->flag)
            {
                LOGT(("my flag captured (%f)\n", it->time));
                m_EnemyPossession = false;
            }
            break;

        case MatchCombatEvent::TYPE_FLAG_RESTORED:
            if(it->flagRestoredEventData.subject == m_game->enemyTeam->flag)
            {
                LOGT(("enemy flag restored (%f)\n", it->time));
                m_MyPossession = false;
            }
            else if (it->flagRestoredEventData.subject == m_game->team->flag)
            {
                LOGT(("my flag restored (%f)\n", it->time));
                m_EnemyPossession = false;
            }
            break;

        case MatchCombatEvent::TYPE_KILLED:
            LOGT(("%s killed by %s (%f)\n", it->killedEventData.subject->name.c_str(), it->killedEventData.instigator->name.c_str(), it->time));

            if (it->killedEventData.subject->team == m_game->enemyTeam)
                m_EnemyBotsAlive = max(0u, m_EnemyBotsAlive - 1);

            if (it->killedEventData.subject->flag)
            {
                if (it->killedEventData.subject->team == m_game->enemyTeam)
                {
                    LOGT(("killed enemy flag holder (%f)\n", it->time));
                    m_EnemyPossession = false;
                }
                else if (it->killedEventData.subject->team == m_game->team)
                {
                    LOGT(("killed my flag holder (%f)\n", it->time));
                    m_MyPossession = false;
                }
            }
            break;

        case MatchCombatEvent::TYPE_RESPAWN:
            LOGT(("respawn\n"));

            m_EnemyBotsAlive = m_game->enemyTeam->members.size();

            break;
        }
    }

    combat_events.clear(); // TODO is it all right?
    return res;
} 

///////////////////////////////////////////////////////////////////////////////////////////////////
uint MyCommander::FindClosestBot(vector<BotInfo*> bots, const Vector2& pos)
{
    assert(bots.size() > 0);

    int closest_index = 0;
    float min_dist = bots[0]->position->squaredDistance(pos);

    for (uint i = 1; i < bots.size(); ++i)
    {
        float dist = bots[i]->position->squaredDistance(pos);
        if (dist < min_dist)
        {
            min_dist = dist;
            closest_index = i;
        }
    }

    assert(closest_index < (int) bots.size());
    return closest_index;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::SetDefendDirs()
{
    float fov = m_level->fieldOfViewAngles[BotInfo::STATE_DEFENDING] * 0.666f;
    uint num_dirs = (uint) ceilf(2.f * PI / fov);

    for (uint i = 0; i < num_dirs; ++i)
    {
        m_BaseDefenceDirs.push_back(Utils::Rotate(Vector2(1.f, 0.f), fov * (float) i));
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////
uint MyCommander::GetOptimalNumDefenders() 
{
    return (uint) (m_DefendersRatio * (float) m_game->team->members.size()); //bots_available.size(); // try to keep the same numer of defenders even after losing bots 
}

///////////////////////////////////////////////////////////////////////////////////////////////////
bool MyCommander::SeeDanger(BotInfo* bot)
{
    return true;
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::DumpCreate()
{
#ifdef _MY_DEBUG
    m_DebugImage = new bitmap_image(m_level->width, m_level->height);
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::DumpDestroy(string name)
{
#ifdef _MY_DEBUG
    m_DebugImage->save_image(name.c_str());
    delete m_DebugImage;
#endif
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::DumpPath(const Path& path, uchar r, uchar g, uchar b)
{
#ifdef _MY_DEBUG
    for (uint i = 0; i < path.size(); ++i)
        m_DebugImage->set_pixel((uint) path[i].x, (uint) path[i].y, r, g, b);
#endif // _MY_DEBUG
}

///////////////////////////////////////////////////////////////////////////////////////////////////
void MyCommander::DumpImage()
{
#ifdef _MY_DEBUG
    for (int x = 0; x < m_level->width; ++x)
    {
        for (int y = 0; y < m_level->height; ++y)
        {
            uchar col = m_level->blockHeights[x + y * m_level->width] > 1 ? 0 : 255;

            m_DebugImage->set_pixel(x, y, col, col, col);
        }
    }
#endif
}

